import json
import os
from datetime import timedelta
import math
import numpy as np
import pandas as pd
from feature_set.base_feature import BaseFeature, RequstData


class AppIdOJKV1(BaseFeature):
    def __init__(self):
        super().__init__()
        self.load_conf()
        self.function_map = {
            'applist_cnt_features': self.applist_cnt_features,
            'applist_top_features': self.applist_top_features,
            'applist_se_features': self.applist_se_features,
            'applist_day_features': self.applist_day_features,
        }

    def load_conf(self):
        app_gcate_df = pd.read_excel(
                os.path.join(
                    self.app_conf_dir,
                    "id",
                    "app_id_ojk_v1",
                    "AF_APP_CATE0813.xlsx")
        )
        fea_names = os.path.join(self.app_conf_dir,"id","app_id_ojk_v1","feature_names.txt")

        with open(fea_names) as f:
            line = f.read()
        self.feature_names = line.split('\n')


        self.app_gcate_df = app_gcate_df[['app_package','classification','classification_sub','downloads']]

        self.is_systems = ['0', '1']
        self.classifications = ['OTHER', 'LIFESTYLE', 'PRODUCTIVITY', 'COMMUNICATION',
                           'TRAVEL_AND_LOCAL', 'ART_AND_DESIGN', 'PERSONALIZATION', 'TOOLS',
                           'ENTERTAINMENT', 'MUSIC_AND_AUDIO', 'PHOTOGRAPHY', 'GAME',
                           'BOOKS_AND_REFERENCE', 'EDUCATION', 'VIDEO_PLAYERS', 'FINANCE',
                           'MEDICAL', 'SHOPPING', 'BUSINESS', 'DATING', 'AUTO_AND_VEHICLES',
                           'FOOD_AND_DRINK', 'PARENTING', 'MAPS_AND_NAVIGATION',
                           'HEALTH_AND_FITNESS', 'SOCIAL', 'NEWS_AND_MAGAZINES', 'BEAUTY',
                           'WEATHER', 'SPORTS', 'HOUSE_AND_HOME', 'EVENTS', 'COMICS',
                           'LIBRARIES_AND_DEMO']
        self.classification_subs = ['OTHER', 'LIFESTYLE', 'PRODUCTIVITY', 'COMMUNICATION',
                               'TRAVEL_AND_LOCAL', 'ART_AND_DESIGN', 'PERSONALIZATION', 'TOOLS',
                               'ENTERTAINMENT', 'MUSIC_AND_AUDIO', 'PHOTOGRAPHY', 'GAME_ACTION',
                               'GAME_ROLE_PLAYING', 'GAME_SIMULATION', 'GAME_CASINO',
                               'BOOKS_AND_REFERENCE', 'GAME_ADVENTURE', 'GAME_PUZZLE',
                               'EDUCATION', 'GAME_CASUAL', 'GAME_SPORTS', 'VIDEO_PLAYERS', 'OJK',
                               'MEDICAL', 'GAME_BOARD', 'OTHER_FINANCE', 'SHOPPING', 'BUSINESS',
                               'DATING', 'AUTO_AND_VEHICLES', 'WEB3', 'FOOD_AND_DRINK',
                               'PARENTING', 'MAPS_AND_NAVIGATION', 'GAME_TRIVIA',
                               'HEALTH_AND_FITNESS', 'SOCIAL', 'WALLET', 'GAME_EDUCATIONAL',
                               'GAME_CARD', 'GAME_RACING', 'NEWS_AND_MAGAZINES', 'GAME_MUSIC',
                               'BEAUTY', 'GAME_WORD', 'BANK', 'GAME_ARCADE', 'CASHLOAN',
                               'WEATHER', 'SPORTS', 'GAME_STRATEGY', 'HOUSE_AND_HOME', 'P2P',
                               'EVENTS', 'INVESTMENT', 'COMICS', 'REALMONEY', 'ACCOUNTIONG',
                               'LOAN_CACULATOR', 'LIBRARIES_AND_DEMO', 'CONSUMERLOAN',
                               'CALCULATOR', 'OHTER_FINANCE', 'KSP', 'INSURANCE', 'CREDIT_CARD',
                               'TAX']
        self.downloads = ['OTHER', '50M+', '10K+', '1M+', '5M+', '10M+', '500K+', '100K+',
                     '50K+', '500M+', '100M+', '5K+', '100+', '1K+', '500+', '50+',
                     '10+', '1B+', '10B+', '5B+', '1+', '0+', '5+']


    def load_request(self, request_data: RequstData):
        app_list = []
        try:
            applist_data = request_data.data_sources["applist_data"]
            app_list = (
                json.loads(applist_data) if type(applist_data) == str else applist_data
            )
        except:
            pass
        if app_list is None or len(app_list) == 0 or type(app_list) != list:
            assert (
                1 == 0
            ), "传入的app_json无法解析或者解析后异常(不是list，或者为空list)"

        apply_time = request_data.apply_time

        user_apps = pd.DataFrame(app_list)
        user_apps = user_apps.merge(self.app_gcate_df, on='app_package', how='left')
        user_apps['classification'] = user_apps['classification'].fillna('OTHER')
        user_apps['classification_sub'] = user_apps['classification_sub'].fillna('OTHER')
        user_apps['downloads'] = user_apps['downloads'].fillna('OTHER')

        user_apps['install_time'] = pd.to_datetime(user_apps['fi_time'], unit='ms')
        user_apps['install_date'] = user_apps['install_time'].dt.normalize()
        user_apps['lastupdate_time'] = pd.to_datetime(user_apps['lu_time'], unit='ms')
        user_apps['lastupdate_date'] = user_apps['lastupdate_time'].dt.normalize()
        user_apps['apply_time'] = pd.to_datetime(apply_time)
        user_apps['apply_date'] = user_apps['apply_time'].dt.normalize()

        user_apps['data_diff'] = (user_apps['apply_date'] - user_apps['install_date']).dt.days
        user_apps['data_diff'] = user_apps['data_diff'].clip(lower=0).fillna(999).astype(int)

        user_apps['isSystem'] = user_apps['isSystem'].fillna(0)
        user_apps['isSystem'] = user_apps['isSystem'].astype(str)

        most_install_time = user_apps['install_time'].value_counts().sort_values(ascending=False).index[0]
        user_apps['is_system'] = np.where(user_apps['install_time'] <= most_install_time, '1', '0')
        self.user_apps = user_apps

    def applist_cnt_features(self):
        """
        计数类特征计算
        """
        user_apps = self.user_apps
        rs_dict={}
        rs_dict['app_cnt_total'] = user_apps['app_package'].count()
        for is_system in self.is_systems:
            rs_dict[f'app_cnt_system_{is_system}'] = user_apps[user_apps['is_system'] == is_system][
                'app_package'].count()
            rs_dict[f'app_rate_system_{is_system}'] = user_apps[user_apps['is_system'] == is_system][
                                                          'app_package'].count() / rs_dict['app_cnt_total']

        for classification in self.classifications:
            rs_dict[f'app_cnt_cate_{classification}'] = user_apps[user_apps['classification'] == classification][
                'app_package'].count()
            rs_dict[f'app_rate_cate_{classification}'] = rs_dict[f'app_cnt_cate_{classification}'] / rs_dict[
                'app_cnt_total']

        for classification in self.classification_subs:
            rs_dict[f'app_cnt_subcate_{classification}'] = user_apps[user_apps['classification_sub'] == classification][
                'app_package'].count()
            rs_dict[f'app_rate_subcate_{classification}'] = rs_dict[f'app_cnt_subcate_{classification}'] / rs_dict[
                'app_cnt_total']

        for download in self.downloads:
            rs_dict[f'app_cnt_downloads_{download}'] = user_apps[user_apps['downloads'] == download][
                'app_package'].count()
            rs_dict[f'app_rate_downloads_{download}'] = rs_dict[f'app_cnt_downloads_{download}'] / rs_dict[
                'app_cnt_total']

        return rs_dict

    def applist_top_features(self):
        rs_dict={}
        user_apps = self.user_apps
        rs_dict['app_cnt_total'] = user_apps['app_package'].count()
        system_grp = user_apps.groupby('isSystem')['app_package'].count().sort_values(ascending=False)
        rs_dict[f'app_top1_system_desc'] = system_grp.index[0]
        rs_dict[f'app_top1_system_cnt'] = system_grp.iloc[0]
        rs_dict[f'app_top1_system_rate'] = rs_dict[f'app_top1_system_cnt'] / rs_dict['app_cnt_total']

        cate_grp = user_apps.groupby('classification')['app_package'].count().sort_values(ascending=False)
        rs_dict[f'app_top1_cate_desc'] = cate_grp.index[0]
        rs_dict[f'app_top1_cate_cnt'] = cate_grp.iloc[0]
        rs_dict[f'app_top1_cate_rate'] = rs_dict[f'app_top1_cate_cnt'] / rs_dict['app_cnt_total']

        rs_dict[f'app_top2_cate_desc'] = cate_grp.index[1]
        rs_dict[f'app_top2_cate_cnt'] = cate_grp.iloc[1]
        rs_dict[f'app_top2_cate_rate'] = rs_dict[f'app_top2_cate_cnt'] / rs_dict['app_cnt_total']

        rs_dict[f'app_top3_cate_desc'] = cate_grp.index[2]
        rs_dict[f'app_top3_cate_cnt'] = cate_grp.iloc[2]
        rs_dict[f'app_top3_cate_rate'] = rs_dict[f'app_top3_cate_cnt'] / rs_dict['app_cnt_total']

        sub_cate_grp = user_apps.groupby('classification_sub')['app_package'].count().sort_values(ascending=False)
        rs_dict[f'app_top1_sub_cate_desc'] = sub_cate_grp.index[0]
        rs_dict[f'app_top1_sub_cate_cnt'] = sub_cate_grp.iloc[0]
        rs_dict[f'app_top1_sub_cate_rate'] = rs_dict[f'app_top1_sub_cate_cnt'] / rs_dict['app_cnt_total']

        rs_dict[f'app_top2_sub_cate_desc'] = sub_cate_grp.index[1]
        rs_dict[f'app_top2_sub_cate_cnt'] = sub_cate_grp.iloc[1]
        rs_dict[f'app_top2_sub_cate_rate'] = rs_dict[f'app_top2_sub_cate_cnt'] / rs_dict['app_cnt_total']

        rs_dict[f'app_top3_sub_cate_desc'] = sub_cate_grp.index[2]
        rs_dict[f'app_top3_sub_cate_cnt'] = sub_cate_grp.iloc[2]
        rs_dict[f'app_top3_sub_cate_rate'] = rs_dict[f'app_top3_sub_cate_cnt'] / rs_dict['app_cnt_total']

        downloads_grp = user_apps.groupby('downloads')['app_package'].count().sort_values(ascending=False)
        rs_dict[f'app_top1_downloads_desc'] = downloads_grp.index[0]
        rs_dict[f'app_top1_downloads_cnt'] = downloads_grp.iloc[0]
        rs_dict[f'app_top1_downloads_rate'] = rs_dict[f'app_top1_downloads_cnt'] / rs_dict['app_cnt_total']

        rs_dict[f'app_top2_downloads_desc'] = downloads_grp.index[1]
        rs_dict[f'app_top2_downloads_cnt'] = downloads_grp.iloc[1]
        rs_dict[f'app_top2_downloads_rate'] = rs_dict[f'app_top2_downloads_cnt'] / rs_dict['app_cnt_total']

        rs_dict[f'app_top3_downloads_desc'] = downloads_grp.index[2]
        rs_dict[f'app_top3_downloads_cnt'] = downloads_grp.iloc[2]
        rs_dict[f'app_top3_downloads_rate'] = rs_dict[f'app_top3_downloads_cnt'] / rs_dict['app_cnt_total']
        return rs_dict


    def applist_se_features(self):
        def _calc_se_values(user_apps, rs_dict, prefix):
            if len(user_apps) > 0:
                min_install_date = user_apps['install_date'].min()
                max_install_date = user_apps['install_date'].max()
                min_lastupdate_date = user_apps['lastupdate_date'].min()
                max_lastupdate_date = user_apps['lastupdate_date'].max()
                apply_date = user_apps['apply_date'].iloc[0]
                rs_dict[f'{prefix}_max_min_install_datediff'] = (max_install_date - min_install_date).days
                rs_dict[f'{prefix}_max_min_install_avg'] = user_apps['app_package'].count() / rs_dict[
                    f'{prefix}_max_min_install_datediff']
                rs_dict[f'{prefix}_max_min_update_datediff'] = (max_lastupdate_date - min_lastupdate_date).days
                rs_dict[f'{prefix}_max_min_update_avg'] = user_apps['app_package'].count() / rs_dict[
                    f'{prefix}_max_min_update_datediff']
                rs_dict[f'{prefix}_apply_min_install_datediff'] = (apply_date - min_install_date).days
                rs_dict[f'{prefix}_apply_max_install_datediff'] = (apply_date - max_install_date).days
                rs_dict[f'{prefix}_apply_min_update_datediff'] = (apply_date - min_lastupdate_date).days
                rs_dict[f'{prefix}_apply_max_update_datediff'] = (apply_date - max_lastupdate_date).days

                user_apps = user_apps.sort_values('install_time')
                user_apps['rank'] = user_apps['install_time'].rank(method='first')
                app_tmp1 = user_apps[['app_package', 'install_time', 'rank']].copy()
                app_tmp2 = user_apps[['app_package', 'install_time', 'rank']].copy()
                app_tmp2['rank'] = app_tmp2['rank'] + 1
                delt_df = app_tmp2.merge(app_tmp1, on='rank', how='outer')
                delt_df.iloc[0, 1] = app_tmp2['install_time'].min()
                delt_df.iloc[-1, 4] = app_tmp2['install_time'].max()
                delt_df['delt_time'] = delt_df['install_time_y'] - delt_df['install_time_x']
                delt_df['delt_date'] = delt_df['delt_time'].dt.days

                install_gap_30d_time_bond = delt_df[delt_df['delt_time'] > timedelta(days=30)]['install_time_y'].max()
                install_gap_60d_time_bond = delt_df[delt_df['delt_time'] > timedelta(days=60)]['install_time_y'].max()

                user_apps_del_30d_gap = user_apps[user_apps['install_time'] > install_gap_30d_time_bond]
                user_apps_del_60d_gap = user_apps[user_apps['install_time'] > install_gap_60d_time_bond]
            else:
                rs_dict[f'{prefix}_max_min_install_datediff'] = -999
                rs_dict[f'{prefix}_max_min_install_avg'] = -999
                rs_dict[f'{prefix}_max_min_update_datediff'] = -999
                rs_dict[f'{prefix}_max_min_update_avg'] = -999
                rs_dict[f'{prefix}_apply_min_install_datediff'] = -999
                rs_dict[f'{prefix}_apply_max_install_datediff'] = -999
                rs_dict[f'{prefix}_apply_min_update_datediff'] = -999
                rs_dict[f'{prefix}_apply_max_update_datediff'] = -999
                rs_dict[f'{prefix}_del_30d_gap_max_min_install_datediff'] = -999
                rs_dict[f'{prefix}_del_30d_gap_max_min_install_avg'] = -999
                rs_dict[f'{prefix}_del_30d_gap_apply_min_install_datediff'] = -999
                rs_dict[f'{prefix}_del_30d_gap_apply_max_install_datediff'] = -999
                rs_dict[f'{prefix}_del_60d_gap_max_min_install_datediff'] = -999
                rs_dict[f'{prefix}_del_60d_gap_max_min_install_avg'] = -999
                rs_dict[f'{prefix}_del_60d_gap_apply_min_install_datediff'] = -999
                rs_dict[f'{prefix}_del_60d_gap_apply_max_install_datediff'] = -999
                return rs_dict

            if len(user_apps_del_30d_gap) > 0:
                min_install_date = user_apps_del_30d_gap['install_date'].min()
                max_install_date = user_apps_del_30d_gap['install_date'].max()
                rs_dict[f'{prefix}_del_30d_gap_max_min_install_datediff'] = (max_install_date - min_install_date).days
                rs_dict[f'{prefix}_del_30d_gap_max_min_install_avg'] = user_apps_del_30d_gap['app_package'].count() \
                                                                       / rs_dict[
                                                                           f'{prefix}_del_30d_gap_max_min_install_datediff']
                rs_dict[f'{prefix}_del_30d_gap_apply_min_install_datediff'] = (apply_date - min_install_date).days
                rs_dict[f'{prefix}_del_30d_gap_apply_max_install_datediff'] = (apply_date - max_install_date).days
            else:
                rs_dict[f'{prefix}_del_30d_gap_max_min_install_datediff'] = -999
                rs_dict[f'{prefix}_del_30d_gap_max_min_install_avg'] = -999
                rs_dict[f'{prefix}_del_30d_gap_apply_min_install_datediff'] = -999
                rs_dict[f'{prefix}_del_30d_gap_apply_max_install_datediff'] = -999

            if len(user_apps_del_60d_gap) > 0:
                min_install_date = user_apps_del_60d_gap['install_date'].min()
                max_install_date = user_apps_del_60d_gap['install_date'].max()
                rs_dict[f'{prefix}_del_60d_gap_max_min_install_datediff'] = (max_install_date - min_install_date).days
                rs_dict[f'{prefix}_del_60d_gap_max_min_install_avg'] = user_apps_del_60d_gap['app_package'].count() \
                                                                       / rs_dict[
                                                                           f'{prefix}_del_60d_gap_max_min_install_datediff']
                rs_dict[f'{prefix}_del_60d_gap_apply_min_install_datediff'] = (apply_date - min_install_date).days
                rs_dict[f'{prefix}_del_60d_gap_apply_max_install_datediff'] = (apply_date - max_install_date).days
            else:
                rs_dict[f'{prefix}_del_60d_gap_max_min_install_datediff'] = -999
                rs_dict[f'{prefix}_del_60d_gap_max_min_install_avg'] = -999
                rs_dict[f'{prefix}_del_60d_gap_apply_min_install_datediff'] = -999
                rs_dict[f'{prefix}_del_60d_gap_apply_max_install_datediff'] = -999
            return rs_dict
        rs_dict={}
        user_apps = self.user_apps
        rs_dict = _calc_se_values(user_apps, rs_dict, 'total')
        rs_dict = _calc_se_values(user_apps[user_apps['is_system'] == '1'], rs_dict, 'system')
        rs_dict = _calc_se_values(user_apps[user_apps['is_system'] == '0'], rs_dict, 'nonsystem')
        rs_dict = _calc_se_values(user_apps[user_apps['classification_sub'].isin(['CASHLOAN', 'OJK', 'P2P'])], rs_dict,
                                  'LOAN')
        for classification in self.classifications:
            rs_dict = _calc_se_values(user_apps[user_apps['classification'] == classification], rs_dict,
                                      f'{classification}_cate')
        for download in self.downloads:
            rs_dict = _calc_se_values(user_apps[user_apps['downloads'] == download], rs_dict, f'{download}')
        return rs_dict

    def applist_day_features(self):
        def density_calc(user_apps, col, window_size):
            date_grp = user_apps[col].value_counts().sort_index().reset_index()
            apply_date = user_apps.loc[0, 'apply_date']
            start_date = apply_date + timedelta(days=-window_size)
            date_list = date_grp[date_grp[col] >= start_date][col].to_list()
            density = 0
            a = start_date
            for b in date_list:
                delt = (b - a).days
                density = density + math.pow(delt, 2)
                a = b
            density = density + math.pow((apply_date - a).days, 2)
            density = math.sqrt(density) / 30
            return density
        user_apps = self.user_apps
        rs_dict = {}
        install_date_grp = user_apps.groupby('install_date')['app_package'].count().sort_index().reset_index().reset_index()
        rs_dict[f'agg_day_num'] = len(install_date_grp)
        rs_dict[f'agg_day_max'] = install_date_grp['app_package'].max()
        rs_dict[f'agg_day_min'] = install_date_grp['app_package'].min()
        rs_dict[f'agg_day_mean'] = install_date_grp['app_package'].mean()

        rs_dict[f'install_density_30d'] = density_calc(user_apps, 'install_date', 30)
        rs_dict[f'install_density_60d'] = density_calc(user_apps, 'install_date', 60)
        rs_dict[f'update_density_30d'] = density_calc(user_apps, 'lastupdate_date', 30)
        rs_dict[f'update_density_60d'] = density_calc(user_apps, 'lastupdate_date', 60)
        return rs_dict

    def gen_features(self, data: RequstData):
        """
        为generator装载数据，并遍调用function_map中的函数，收集并整理计算后的结果，如需要增加特征名称前缀，请在子类中自行实现
        """
        try:
            self.load_request(data)
        except AssertionError as e:
            rs_dict = {k: '' for k in self.feature_names[0:10]}
            rs_num = {k: -999 for k in self.feature_names[11:]}
            rs_dict.update(rs_num)
            return rs_dict

        rs_dict = {}
        for k, func in self.function_map.items():
            rs_dict.update(func())

        rs = {}
        for k, v in rs_dict.items():
            k = k.replace("+","")

            if (v is not None) and type(v) != str:
                if isinstance(v,np.int64): # int64格式会导致json序列化失败
                    rs[k] = int(v)
                else:
                    rs[k] = round(v, 4)
            else:
                rs[k] = v

        result = { k:rs[k] for k in self.feature_names}
        return result